import {expect} from 'chai'
import {afterEach, describe} from 'mocha'
import * as sinon from 'sinon'
import * as svnSource from '../src/svn-source'
import { CommandOptions } from '@flow-step/step-toolkit/lib/exec/command'
import { IGitSourceParams } from '../src/git-source-params'
import * as step from '@flow-step/step-toolkit'
// @ts-ignore
import fs from 'fs'

describe('svn source', () => {
  const svn = new svnSource.SvnSource()
  let options: IGitSourceParams = new IGitSourceParams()
  let stubExportEnvVar: sinon.SinonStub
  let stubAddOutput: sinon.SinonStub

  const oneLineLogMessage = [
          "------------------------------------------------------------------------",
          "r123 | author | 2021-01-01 00:00:00 +0800 (Fri, 01 Jan 2021) | 1 line",
          "",
          "the commit message",
          "------------------------------------------------------------------------"
        ]
  const twoLineLogMessage = [
    "------------------------------------------------------------------------",
    "r123 | author | 2021-01-01 00:00:00 +0800 (Fri, 01 Jan 2021) | 2 line",
    "",
    "the commit message line 1",
    "the commit message line 2",
    "------------------------------------------------------------------------"
  ]

  beforeEach(() => {
    options = new IGitSourceParams()
    options.user = "username"
    options.password = "password"
    options.projectDir = "/root/yunxiao/d/w/uuid/workspace/project1"
    options.repo = "svn://svnserver/svn/project1"
    options.branch = "trunk"
    options.order = 1
    stubExportEnvVar = sinon.stub(step, 'exportEnvVar')
    stubAddOutput = sinon.stub(step, 'addOutput')
    sinon.stub(fs,'mkdirSync')
  })

  afterEach(() => {
    sinon.restore()
  })

  it('should update if .svn file exists', async () => {
    givenFileExists( "/root/yunxiao/d/w/uuid/workspace/project1/trunk/.svn");
    const fakeCall = makeUpdateFakeCall(oneLineLogMessage)

    await svn.handleSvnSource(options)

    sinon.assert.calledWith(fakeCall, 'svn', ['update', '--username', 'username', '--password', 'password'],
        sinon.match({cwd: '/root/yunxiao/d/w/uuid/workspace/project1/trunk'}))
    sinon.assert.calledWith(fakeCall, 'svn', ['log', '--username', 'username', '--password', 'password', '-l', '1'],
        sinon.match({cwd: '/root/yunxiao/d/w/uuid/workspace/project1/trunk'}))
  })


  it('should checkout if .svn file not exists', async () => {
    const fakeCall = makeCheckoutFakeCall(oneLineLogMessage)

    await svn.handleSvnSource(options)

    sinon.assert.calledWith(fakeCall, 'svn',
        ['checkout', 'svn://svnserver/svn/project1/trunk', '--username', 'username', '--password', 'password', '--no-auth-cache'],
        sinon.match({cwd: '/root/yunxiao/d/w/uuid/workspace/project1'}))
  })

  it('should return exit code', async () => {
    createFakeStepCall([
      {subCommand: "update", exitCode: 0},
      {subCommand: "checkout", exitCode: -1}
    ]);

    expect(await svn.handleSvnSource(options)).to.be.equal(-1)
  })

  it('should write svn info', async () => {
    makeCheckoutFakeCall(oneLineLogMessage)

    await svn.handleSvnSource(options)

    sinon.assert.calledWith(stubExportEnvVar, 'CI_SOURCE_NAME', 'project1')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_SOURCE_NAME_1', 'project1')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_ID', 'r123')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_ID_1', 'r123')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_REF_NAME', 'trunk')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_REF_NAME_1', 'trunk')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_TITLE', 'the commit message')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_TITLE_1', 'the commit message')

    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_SOURCE_NAME=project1')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_SOURCE_NAME_1=project1')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_ID=r123')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_ID_1=r123')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_REF_NAME=trunk')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_REF_NAME_1=trunk')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_TITLE=the commit message')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_TITLE_1=the commit message')
    sinon.assert.calledWith(stubAddOutput, 'svn://svnserver/svn/project1.trunk.commitMsg=[{"commitTime":"2021-01-01 00:00:00 +0800 (Fri, 01 Jan 2021)", "commitMsg":"the commit message", "commitId":"r123", "commitAuthor":"author"}]')
  })

  it('should write svn info with multiline commit message', async () => {
    makeCheckoutFakeCall(twoLineLogMessage)

    await svn.handleSvnSource(options)

    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_TITLE', 'the commit message line 1the commit message line 2')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_COMMIT_TITLE_1', 'the commit message line 1the commit message line 2')

    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_TITLE=the commit message line 1the commit message line 2')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_COMMIT_TITLE_1=the commit message line 1the commit message line 2')
  })

  it('should handle repo endpoint end with slash in update', async () => {
    givenFileExists( "/root/yunxiao/d/w/uuid/workspace/project1/trunk/.svn");
    options.repo = "svn://svnserver/svn/project1/"

    const fakeCall = makeUpdateFakeCall(oneLineLogMessage);

    await svn.handleSvnSource(options)

    sinon.assert.calledWith(fakeCall, 'svn', ['update', '--username', 'username', '--password', 'password'],
        sinon.match({cwd: '/root/yunxiao/d/w/uuid/workspace/project1/trunk'}))
    sinon.assert.calledWith(fakeCall, 'svn', ['log', '--username', 'username', '--password', 'password', '-l', '1'],
        sinon.match({cwd: '/root/yunxiao/d/w/uuid/workspace/project1/trunk'}))

    sinon.assert.calledWith(stubExportEnvVar, 'CI_SOURCE_NAME', 'project1')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_SOURCE_NAME_1', 'project1')

    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_SOURCE_NAME=project1')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_SOURCE_NAME_1=project1')
  })

  it('should handle repo endpoint end with slash in checkout', async () => {
    options.repo = "svn://svnserver/svn/project1/"
    const fakeCall = makeCheckoutFakeCall(oneLineLogMessage);

    await svn.handleSvnSource(options)

    sinon.assert.calledWith(fakeCall, 'svn',
        ['checkout', 'svn://svnserver/svn/project1/trunk', '--username', 'username', '--password', 'password', '--no-auth-cache'],
        sinon.match({cwd: '/root/yunxiao/d/w/uuid/workspace/project1'}))

    sinon.assert.calledWith(stubExportEnvVar, 'CI_SOURCE_NAME', 'project1')
    sinon.assert.calledWith(stubExportEnvVar, 'CI_SOURCE_NAME_1', 'project1')

    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_SOURCE_NAME=project1')
    sinon.assert.calledWith(stubAddOutput, 'outputsToEnv.CI_SOURCE_NAME_1=project1')
  })

  function createFakeStepCall(calls: {subCommand?: String, lines?: string[], exitCode: number}[] = []) {
    const fakeCall = sinon.fake((command: string, args?: string[], options?: CommandOptions) => {
      let subCommand = calls.find(commandOutput => commandOutput.subCommand == args?.[0]);
      subCommand?.lines?.forEach(line =>
          options?.listener?.stdoutLine!(line)
      )
      return Promise.resolve((subCommand || {exitCode: -1}).exitCode)
    })
    sinon.replace(step.exec, 'call', fakeCall);
    return fakeCall
  }

  function givenFileExists(filePath: string) {
    sinon.stub(fs, 'existsSync').withArgs(filePath).returns(true)
  }

  function makeUpdateFakeCall(stdLines: string[]) {
    return createFakeStepCall([
      {subCommand: "update", exitCode: 0},
      {subCommand: "log", exitCode: 0, lines: stdLines}
    ]);
  }

  function makeCheckoutFakeCall(stdLines: string[]) {
    return createFakeStepCall([
      {subCommand: "update", exitCode: 0},
      {subCommand: "checkout", exitCode: 0},
      {subCommand: "log", exitCode: 0, lines: stdLines}
    ]);
  }
})
